再來複習一下這張圖 XD,右半邊已經完成了,接下來就換實作左半邊的部分,就先建立 MediaBrowser,這個元件的功能為連結 MediaBrowserService,取得 MediaBrowserService 內的 sessionToken,就可以建立 MediaController 了,有了 MediaController 就如同取得遙控器可以播歌,並可以知道目前播放的情況(播放中、在播什麼歌)。
圖上面的左半邊上面寫的是 Activity,意思是元件可以放在 Acitivity 內,但直接放在 Acitivity 內,就不能重複使用了,其他頁面也需要播放功能,就只能再重新寫一次了。因此在 uamp 專案就將這個邏輯寫在 MusicServiceConnection 內,其他要用的頁面就可以使用這個元件,就可以有播放功能了。
先來寫 MediaBrowser 的部分,在 constructor 會傳入 serviceComponent 參數,要傳繼承 media browse service 的名稱,在程式內為 MusicService,寫法是這樣 ComponentName(context, MusicService::class.java),然後再設定 MediaBrowserConnectionCallback,就可以得到連接的結果,再下 connect 連結 service。
private val mediaBrowserConnectionCallback = MediaBrowserConnectionCallback(context)
private val mediaBrowser = MediaBrowserCompat(
context,
serviceComponent,
mediaBrowserConnectionCallback, null
).apply { connect() }
在 Callback 內連上後,就可以取得 media session,來建立 MediaController 了。
private lateinit var mediaController: MediaControllerCompat
......
private inner class MediaBrowserConnectionCallback(private val context: Context) :
MediaBrowserCompat.ConnectionCallback() {
override fun onConnected() {
Log.d(TAG, "MediaBrowserConnectionCallback onConnected")
// Get a MediaController for the MediaSession.
mediaController = MediaControllerCompat(context, mediaBrowser.sessionToken).apply {
registerCallback(MediaControllerCallback())
}
}
override fun onConnectionSuspended() {
Log.d(TAG, "MediaBrowserConnectionCallback onConnectionSuspended")
}
override fun onConnectionFailed() {
Log.d(TAG, "MediaBrowserConnectionCallback onConnectionFailed")
}
}
在建立 MediaController 又看到一個 callback 了 XD,這個 callback 是做什麼呢?分別可以接收播放情況(State) 的改變(ex: 播放中、暫停中、向前快轉.....等)。另外一個是播放內容(MediaMetadataCompat) 的改變,前幾天在介紹播放時,有將歌曲轉成這個結構,在這邊就可以接收到播放內容的資料
val playbackState = MutableLiveData<PlaybackStateCompat>()
.apply { postValue(EMPTY_PLAYBACK_STATE) }
val nowPlaying = MutableLiveData<MediaMetadataCompat>()
.apply { postValue(NOTHING_PLAYING) }
......
private inner class MediaControllerCallback : MediaControllerCompat.Callback() {
override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {
Log.d(TAG, "MediaControllerCallback onPlaybackStateChanged:$state")
playbackState.postValue(state ?: EMPTY_PLAYBACK_STATE)
}
override fun onMetadataChanged(metadata: MediaMetadataCompat?) {
Log.d(TAG, "MediaControllerCallback onMetadataChanged:$metadata")
nowPlaying.postValue(
if (metadata?.id == null) {
NOTHING_PLAYING
} else {
metadata
}
)
}
}
在 ViewModel 傳入 MusicServiceConnection 元件,就可以來寫串接播放功能的最後一哩啦!透過
mediaController 取得 transportControls,就像是遙控器的功能。
val transportControls: MediaControllerCompat.TransportControls
get() = mediaController.transportControls
前面的 if 主要是判斷現在在播的歌和點擊的歌是是否是同一首,如果是播放中就暫停,暫停中就播放。再來就是播放別的歌啦,使用 transportControls 內的 playFromMediaId,傳入歌曲的 id,在 MusicService 內的 onPrepareFromMediaId,就可以收到歌曲的 id,開始播歌。
fun playMedia(mediaItem: Song, pauseAllowed: Boolean = true) {
val nowPlaying = musicServiceConnection.nowPlaying.value
val transportControls = musicServiceConnection.transportControls
val isPrepared = musicServiceConnection.playbackState.value?.isPrepared ?: false
if (isPrepared && mediaItem.id.toString() == nowPlaying?.id) {
musicServiceConnection.playbackState.value?.let { playbackState ->
when {
playbackState.isPlaying ->
if (pauseAllowed) transportControls.pause() else Unit
playbackState.isPlayEnabled -> transportControls.play()
else -> {
Log.w(
TAG, "Playable item clicked but neither play nor pause are enabled!" +
" (mediaId=${mediaItem.id.toString()})"
)
}
}
}
} else {
transportControls.playFromMediaId(mediaItem.id.toString(), null)
}
}
Bingo!可以播音樂啦!覺得有點感動 XD
程式碼在這,分支名稱(day14_mediacontroller): Fancy/day14_mediacontroller